package com.foreveross.project.usercenterserver.security.config;

import com.alibaba.druid.util.StringUtils;
import com.foreveross.project.usercenterserver.common.constant.CachesEnum;
import com.foreveross.project.usercenterserver.common.constant.GlobalConstant;
import com.foreveross.project.usercenterserver.exception.CustomException;
import com.foreveross.project.usercenterserver.oauth2.service.CaptchaService;
import com.foreveross.project.usercenterserver.security.CustomWebAuthenticationDetails;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * AbstractUserDetailsAuthenticationProvider
 实现了AuthenticationProvider提供的authenticate和support方法。
 在authenticate方法中做了如下的操作：
 1.  根据用户提交的用户名从缓存中查询UserDetails
 2.  若没有在缓存中查询到UserDetails，那么就调用retrieveUser检索UserDetails，这个方法是一个抽象方法，在子类中实现；若在子类中没有查询到UserDetails，那么就会抛出UsernameNotFoundException，在AbstractUserDetailsAuthenticationProvider捕获了这个异常并且进行了处理
 3. 接着开始检查UserDetails(不论是从缓存中获取的UserDetails，还是检索出来的UserDetails，都要进行检查)，主要包括的检测有user是否已经过期，是否已经被锁定等等，在AbstractUserDetailsAuthenticationProvider没有检测用户输入的密码是否正确，在检查这一步调用了抽象
 additionalAuthenticationChecks，所有在子类中要实现密码是否匹配的检测
 4.  若通过了所有的检测，那么就判断是否这个UserDetails已经放入到了缓存中，若没有那么就放入到缓存中。
 5.  最后返回一个包含了用户完整信息的UsernamePasswordAuthenticationToken，包括用户名，密码，权限等等。
 */
@Component
public class CustomAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

    @Value("${oauth2.granttype.password.captcha:false}")
    private boolean passwordCaptcha;

    @Autowired
    UserDetailsService userService;

    @Autowired
    PasswordEncoder passwordEncoder;

    @Autowired
    CaptchaService captchaService;

    //所有在子类中要实现密码是否匹配的检测
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails,
                                                  UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        if (authentication.getCredentials() == null) {
            this.logger.debug("Authentication failed: no credentials provided");
            throw new BadCredentialsException(this.messages
                    .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "bBad credentials"));
        } else {
            String presentedPassword = authentication.getCredentials().toString();
            if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
                this.logger.debug("Authentication failed: password does not match stored value");
                throw new BadCredentialsException(this.messages
                        .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "aBad credentials"));
            }
        }
    }

    //retrieveUser检索UserDetails，这个方法是一个抽象方法，在子类中实现；若在子类中没有查询到UserDetails，那么就会抛出UsernameNotFoundException，在AbstractUserDetailsAuthenticationProvider捕获了这个异常并且进行了处理
    @Override
    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        // 添加额外处理，如验证码等
        Object details = authentication.getDetails();
        if (details instanceof CustomWebAuthenticationDetails) {
            CustomWebAuthenticationDetails customWebAuthenticationDetails = (CustomWebAuthenticationDetails) details;
            String captcha = captchaService.getCaptcha(CachesEnum.GraphCaptchaCache, customWebAuthenticationDetails.getGraphId());
            if (!StringUtils.equalsIgnoreCase(customWebAuthenticationDetails.getInputVerificationCode(), captcha)) {
                //throw new VerificationCodeException("验证码错误a！");
            }
            captchaService.removeCaptcha(CachesEnum.GraphCaptchaCache, customWebAuthenticationDetails.getGraphId());
        } else if (details instanceof LinkedHashMap<?, ?>) {
            if (passwordCaptcha) {
                @SuppressWarnings("unchecked")
                Map<String, String> map = (Map<String, String>) details;
                if (map.containsKey("grant_type") && StringUtils.equals("password", map.get("grant_type"))) {

                    if (map.containsKey("graphId") && map.containsKey(GlobalConstant.VERIFICATION_CODE)) {
                        String graphId = map.get("graphId");
                        String captcha = captchaService.getCaptcha(CachesEnum.GraphCaptchaCache, graphId);
                        if (!StringUtils.equalsIgnoreCase(map.get(GlobalConstant.VERIFICATION_CODE), captcha)) {
                            throw new CustomException("验证码错误！");
                        }
                        captchaService.removeCaptcha(CachesEnum.GraphCaptchaCache, graphId);

                    } else {
                        throw new CustomException("验证码错误！");
                    }
                }
            }

        }

        try {
            UserDetails loadedUser = userService.loadUserByUsername(username);

            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException(
                        "UserDetailsService returned null, which is an interface contract violation");
            }

            return loadedUser;
        } catch (UsernameNotFoundException ex) {
            throw ex;
        } catch (InternalAuthenticationServiceException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
        }
    }

    @Override
    public boolean supports(Class<?> arg0) {
        return true;
    }

    private Collection<? extends GrantedAuthority> getAuthorities(String roles) {
        return AuthorityUtils.commaSeparatedStringToAuthorityList(roles);
    }
}
